package org.jpropelleralt.jview.ref.impl;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;

import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.JToggleButton.ToggleButtonModel;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.box.Boxes;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.jview.JView;
import org.jpropelleralt.ref.Ref;
import org.jpropelleralt.ref.impl.RefDefault;
import org.jpropelleralt.view.View;
import org.jpropelleralt.view.Views;
import org.jpropelleralt.view.update.UpdateManager;

/**
 * An editing {@link JView} for a {@link Ref} with value of type {@link Boolean}
 */
public class BooleanView implements JView {

	/**
	 * Types of control
	 */
	public enum ControlType {
		/**
		 * Display as {@link JCheckBox}
		 */
		CHECK_BOX,
		
		/**
		 * Display as {@link JToggleButton}
		 */
		BUTTON
	}
	
	private final Ref<Boolean> value;
	private final Ref<String> name;
	private final Ref<Boolean> locked;
	
	private final UpdateManager updateManager;

	private final JToggleButton toggle;

	private final AutoButtonModel model;

	private BooleanView(final Ref<Boolean> value, Ref<String> name, Ref<Boolean> locked, UpdateManager updateManager, ControlType type) {
		super();
		this.value = value;
		this.name = name;
		this.locked = locked;
		
		if (type == ControlType.CHECK_BOX) {
			toggle = new LinkingJCheckBox(this);
		} else {
			toggle = new LinkingJToggleButton(this);
		}
		
		model = new AutoButtonModel();
		toggle.setModel(model);
		
		toggle.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Boolean vBool = value.get();
				if (vBool != null) {
					value.set(!vBool);
				}
			}
		});
		
		this.updateManager = updateManager;
		updateManager.registerUpdatable(this);

		value.features().addView(this);
		name.features().addView(this);
		if (locked != null) {
			locked.features().addView(this);
		}
		
		//Initial update
		update();
	}
	
	@Override
	public void update() {

		model.fire();
		
		//Update the name display
		String s = name.get();
		if (s == null) {
			s = "";
		} 
		if (!s.equals(toggle.getText())) {
			toggle.setText(s);
		}
	}

	@Override
	public void changes(Map<Box, Change> changes) {
		updateManager.updateRequiredBy(this);
	}
	
	private static Boolean booleanTrue = new Boolean(true);

	private class AutoButtonModel extends ToggleButtonModel {
		public void fire() {
			fireStateChanged();
		}
		@Override
		public boolean isSelected() {
			//Treat null as false/join ##java
			return booleanTrue.equals(value.get());
		}
		
		@Override
		public boolean isEnabled() {
			return (value.get()!=null) && (!Boxes.isTrue(locked));
		}
	}
	
	
	/**
	 * Get the {@link JToggleButton} used for display/editing
	 * 
	 * @return		The {@link JCheckBox}
	 */
	public JToggleButton getComponent() {
		return toggle;
	}
	
	@Override
	public boolean selfNaming() {
		//Displays name of property as well as its value
		return true;
	}
	
	@Override
	public Format format() {
		return Format.SINGLE_LINE;
	}
	
	/**
	 * Make a new {@link BooleanView} with checkbox display
	 * @param value		The {@link Boolean} value to display
	 * @param name		The name to display in control
	 * @return			A new {@link BooleanView}
	 */
	public static BooleanView createCheck(Ref<Boolean> value, String name) {
		return new BooleanView(value, RefDefault.create(name), null, Views.updateManager(), ControlType.CHECK_BOX);
	}

	/**
	 * Make a new {@link BooleanView} with checkbox display
	 * @param value		The {@link Boolean} value to display
	 * @param name		The name to display in control
	 * @return			A new {@link BooleanView}
	 */
	public static BooleanView createCheck(Ref<Boolean> value, Ref<String> name) {
		return new BooleanView(value, name, null, Views.updateManager(), ControlType.CHECK_BOX);
	}

	/**
	 * Make a new {@link BooleanView} with checkbox display
	 * @param value				The {@link Boolean} value to display
	 * @param name				The name to display in control
	 * @param updateManager		The {@link UpdateManager} to use
	 * @return					A new {@link BooleanView}
	 */
	public static BooleanView createCheck(Ref<Boolean> value, Ref<String> name, UpdateManager updateManager) {
		return new BooleanView(value, name, null, updateManager, ControlType.CHECK_BOX);
	}
	
	/**
	 * Make a new {@link BooleanView} with button display
	 * @param value		The {@link Boolean} value to display
	 * @param name		The name to display in control
	 * @return			A new {@link BooleanView}
	 */
	public static BooleanView createButton(Ref<Boolean> value, String name) {
		return new BooleanView(value, RefDefault.create(name), null, Views.updateManager(), ControlType.BUTTON);
	}

	/**
	 * Make a new {@link BooleanView} with button display
	 * @param value		The {@link Boolean} value to display
	 * @param name		The name to display in control
	 * @return			A new {@link BooleanView}
	 */
	public static BooleanView createButton(Ref<Boolean> value, Ref<String> name) {
		return new BooleanView(value, name, null, Views.updateManager(), ControlType.BUTTON);
	}

	/**
	 * Make a new {@link BooleanView} with button display
	 * @param value				The {@link Boolean} value to display
	 * @param name				The name to display in control
	 * @param updateManager		The {@link UpdateManager} to use
	 * @return					A new {@link BooleanView}
	 */
	public static BooleanView createButton(Ref<Boolean> value, Ref<String> name, UpdateManager updateManager) {
		return new BooleanView(value, name, null, updateManager, ControlType.BUTTON);
	}
	
	/**
	 * Make a new {@link BooleanView} with button display
	 * @param value				The {@link Boolean} value to display
	 * @param name				The name to display in control
	 * @param locked			While this is true, view will not permit editing (control disabled) 
	 * @param updateManager		The {@link UpdateManager} to use
	 * @param type 				The {@link ControlType} to use for display
	 * @return					A new {@link BooleanView}
	 */
	public static BooleanView create(Ref<Boolean> value, Ref<String> name, Ref<Boolean> locked, UpdateManager updateManager, ControlType type) {
		return new BooleanView(value, name, locked, updateManager, type);
	}

	/**
	 * Make a new {@link BooleanView} with button display
	 * @param value				The {@link Boolean} value to display
	 * @param name				The name to display in control
	 * @param locked			While this is true, view will not permit editing (control disabled) 
	 * @param type 				The {@link ControlType} to use for display
	 * @return					A new {@link BooleanView}
	 */
	public static BooleanView create(Ref<Boolean> value, Ref<String> name, Ref<Boolean> locked, ControlType type) {
		return new BooleanView(value, name, locked, Views.updateManager(), type);
	}
	
	/**
	 * This is the same as the parent {@link JComponent}, 
	 * the only difference is that it remembers its {@link JView}, 
	 * so that as long as the {@link JComponent} is in
	 * use, the {@link JView} survives as well. Otherwise, the 
	 * {@link JView} can be GCed while the {@link JComponent} is still 
	 * in use, stopping the {@link JComponent} being updated.
	 * Otherwise, it is very easy for the {@link JView} to be collected,
	 * since a {@link View} is NOT kept alive by the data it is
	 * watching, and is generally not of much interest after its
	 * component has been added to a UI. 
	 */
	public static class LinkingJCheckBox extends JCheckBox {
		private final JView owner;
		private LinkingJCheckBox(JView owner) {
			super();
			this.owner = owner;
		}
		/**
		 * Get the owner {@link JView}
		 * @return	The owner of this component
		 */
		public JView owner() {
			return owner;
		}
	}
	
	/**
	 * This is the same as the parent {@link JComponent}, 
	 * the only difference is that it remembers its {@link JView}, 
	 * so that as long as the {@link JComponent} is in
	 * use, the {@link JView} survives as well. Otherwise, the 
	 * {@link JView} can be GCed while the {@link JComponent} is still 
	 * in use, stopping the {@link JComponent} being updated.
	 * Otherwise, it is very easy for the {@link JView} to be collected,
	 * since a {@link View} is NOT kept alive by the data it is
	 * watching, and is generally not of much interest after its
	 * component has been added to a UI. 
	 */
	public static class LinkingJToggleButton extends JToggleButton {
		private final JView owner;
		private LinkingJToggleButton(JView owner) {
			super();
			this.owner = owner;
		}
		/**
		 * Get the owner {@link JView}
		 * @return	The owner of this component
		 */
		public JView owner() {
			return owner;
		}
	}
}
